<?php
/**
 * @author Mcfly
 * @version 1.0
 * Данный класс управляет списоком пользователей.
 * 
 * Таблица со списком пользователей: pref_user_list
 * 
 */
class UserList extends A_Content 
{
	const 
		_xml_config = "user_list.xml";
	private 
		$_flag_index,			// список флагов пользователя. логическая маска для битового поля pref_user_list.flags
		$_flag_defaults,		// список флагов и значений по умолчанию 
		$_password_md5, 		// флаг указывающий на то хранится пароль в зашифрованном md5 виде или нет
		$_profile_class_name,	// имя класса профайла
		$_flag_names;           //список человеских названий флагов
	
	public function __construct($top,$parent)
	{
		
		parent::__construct($top,$parent);
		// TODO неплохо было бы продумать механизм кеширования этого хозяйства, для того чтобы каждый раз не парсить xml

		// TODO парсинг данных о русском названии флагов и инклуд класса с профайлом
		
		$xmlDoc = $this->_top->Utils->ParseXML($top->Utils->fixDirPath($top->_config->general->config_path).self::_xml_config);
		// Заполняем массив с флагами
		$flagNodes = $xmlDoc->getElementsByTagName("flag");
		if ($flagNodes!=null) {
			$totalFlags = $flagNodes->getLength();
			for ($f=0;$f<$totalFlags;$f++) {
				$flagNode = $flagNodes->item($f);
				$this->_flag_index[$flagNode->getAttribute('key')] = $flagNode->getAttribute('bit');
				$this->_flag_defaults[$flagNode->getAttribute('key')] = $flagNode->getAttribute('default');
				$this->_flag_names[$flagNode->getAttribute('key')] = $flagNode->getAttribute('name');
			}
		}
		
		// Читаем флаг по хешированию пароля
		$hashNodes = $xmlDoc->getElementsByTagName("passwordHash");
		if ($hashNodes!=null) {
			$hashNode = $hashNodes->item(0);
			$this->_password_md5 = (int) $hashNode->firstChild->nodeValue;
		} else {
			$this->_password_md5 = 1;
		}
		
		// Читаем данные о файле с классом профайла
		$profoleNodes = $xmlDoc->getElementsByTagName("profile");
		if ($profoleNodes!=null) {
			$profoleNode = $profoleNodes->item(0);
			$this->_profile_class_name = $profoleNode->getAttribute('className');
			$profile_class_path = $profoleNode->getAttribute('classPath');
			if (!file_exists($profile_class_path)) {
				throw $this->_top->Utils->PushError("Can't find file with profile class $profile_class_path", "Не найден класс с файлом профайла");
			} else {
				require_once $profile_class_path;
			}
		} else {
			throw $this->_top->Utils->PushError("Can't find info about profile class in ini file", "Не найдена информация о классе профайла в настроечном файле");
		}
				
		return true;
	}
	
	
    public function getFlagNames(){
        return $this->_flag_names;
    }

    public function getFlagBits(){
        return $this->_flag_index;
    }	
	
	
	public function __get($param)
	{
		if ($param=="_password_md5") {
			return $this->_password_md5;
		} elseif ($param=="_profile_class_name") {
			return $this->_profile_class_name;
		} else {
			return false;
		}
	}
	
	
	
	/**
	 * Выбирает пользователья по базовому параметру user_id
	 * 
	 * @param int $id
	 * @return User object
	 */
	public function getById($id)
	{
		if (!isset($this->_cashe_obj[$id])) {
			$this->_cashe_obj[$id] = new User($this->_top,$this,$id);
		}
		
		return $this->_cashe_obj[$id];
	}
	
	public function add()
	{
		return false;
	}
	
	
	/**
	 * Метод обязательный для абстрактного класса создает запись в таблице pref_user_list 
	 * и возвращает созданного пользователя
	 * 
	 * @return User object
	 **/
	public function addUser($login)
	{
		$check_login = $this->UserSearch();
		$check_login->login = $login;
		$check_login->Make();
		
		if ($check_login->length()>0) {
			throw $this->_top->Utils->PushError("User with such Login already exsists","Пользователь с таким логином уже существует");
		} else {			
			$login = $this->_top->Utils->prepareValueForSQL($login);
			$this->_top->_sql->exeDummy("INSERT INTO ".TAB_PREF."user_list(login) VALUES('$login')");
			$new_user = $this->getById($this->_top->_sql->last_insert_id());
			
			$flag_list = array_keys($this->_flag_defaults);
			for ($f=0;$f<count($flag_list);$f++) {
				$flag_name = $flag_list[$f];
				$new_user->flag->$flag_name = $this->_flag_defaults[$flag_name];
			}
			return $new_user;
		}
	}
	
	
	/**
	 * Возвращает класс UserSearch
	 *
	 * @return UserSearch object
	 */
	public function UserSearch()
	{
		return new UserSearch($this->_top,$this);
	}
	
	public function getFlagIndex($flag_name)
	{
		if (!isset($this->_flag_index[$flag_name])) throw $this->_top->Utils->PushError("No such user flag","Попытка обращения к несуществующему флагу пользователя");
		return $this->_flag_index[$flag_name];
	}	
	
	//расшифровывает флаги в виде строк, разделенных $aSeparator
	public function getCurrentFlags($aUser, $aSeparator){
//	   return Zend_Debug::dump($this->_flag_names, null, false);
        $val = $aUser->flag->value;
        $res = "";
        
        foreach($this->_flag_index as $sysname => $i){
            if( $this->_top->BitMask->bitValue($val, $i) ){ //есть такой флаг
                if( $res ) //разделитель
                    $res .= $aSeparator;            
                $res .= $this->_flag_names[$sysname];
            }
        }        
        return $res;
	}	
	
}

/**
 * Класс производящий поиск по таблице пользователей
 * 
 * Пример использования: 
 * $search = new UserSearch();
 * $search->login = "mcfly";
 * $search->flag->is_vip = 1;
 * $res = $search->Make();
 *
 */
class UserSearch
{
	private $_limits = null, $_total_amount=null;
	private 
		$_top,$_user_list,$search_params = array(),$found = false;
	
	function __construct($top, $user_list)
	{
		$this->_top = $top;
		$this->_user_list = $user_list;
	}
	
	
	function __get($param)
	{
		if ($param=="flag") return new UserSearchFlag($this->_top,$this);
	}

	
	public function setLimits($from_row,$amount) {
		// TODO - need to make validation here
		$this->_limits = array($from_row,$amount);
		return $this;
	}	

	public function __set($param,$value)
	{
		if ($param=="password") return false;
		else {
			$value = $this->_top->Utils->prepareValueForSQL($value);
			if( $value != "" ){
                $value = str_replace('*', '%', $value);
                array_push($this->search_params," AND $param LIKE '$value'");
			}
		}
	}

	/**
	 * метод добавляет в условия поиска 
	 *
	 * @param string $flag_name - название флага
	 * @param int $value - значение флага, 1 или 0
	 */
	public function addFlagCondition($flag_name,$value)
	{
		$mask = $this->_top->BitMask->makeBitMask($this->_user_list->getFlagIndex($flag_name));
		$sign = $value==1 ? "=" : "<>";
		array_push($this->search_params," AND (flags &  $mask) $sign $mask");
	}
	
	public function getSQL(){
	   return "SELECT user_id FROM ".TAB_PREF."user_list WHERE 1 ".implode(" ",$this->search_params).
		  ($this->_limits!==null ? " LIMIT {$this->_limits[0]},{$this->_limits[1]}" : '');
	}
	
	
	/**
	 * Собирает все что указано в качестве аргументов поиска 
	 * @return array массив объектов User найденных пользователей 
	 */
	public function Make()
	{
        //определим общее кол-во записей
//        echo "SELECT count(*) as c FROM ".TAB_PREF."user_list WHERE 1 ".implode(" ",$this->search_params);
		$get_amount = $this->_top->_sql->exe("SELECT count(*) as c FROM ".TAB_PREF."user_list WHERE 1 ".implode(" ",$this->search_params));
		$this->_total_amount = $get_amount[0]['c'];
		
		$this->found = array();
		
//		echo "SELECT user_id FROM ".TAB_PREF."user_list WHERE 1 ".implode(" ",$this->search_params).
//		  ($this->_limits!==null ? " LIMIT {$this->_limits[0]},{$this->_limits[1]}" : '');
		$users = $this->_top->_sql->exe("SELECT user_id FROM ".TAB_PREF."user_list WHERE 1 ".implode(" ",$this->search_params).
		  ($this->_limits!==null ? " LIMIT {$this->_limits[0]},{$this->_limits[1]}" : '')		
		);
	  
        for ($u=0;$u<count($users);$u++)
			array_push($this->found,$users[$u]['user_id']);

		return true;
	}
	
	/**
	 * Возвращает количество найденных пользователей
	 * @return int
	 */
	public function length()
	{
		if (!$this->found) return false;
		return count($this->found);
	}
	
	public function total_length() {
//	   if (!$this->found) return false;
		return $this->_total_amount;
	}	
	
	/**
	 * Возвращаяет пользователей по индексу из массива найденных пользователей
	 * @param int $idx - индекс пользователя в списке найденных пользователей
	 * @return User
	 */
	public function item($idx)
	{
		if (!$this->found) return false;
		return $this->_top->UserList->getById($this->found[$idx]);
	}
}


/**
 * Класс организующий поиск по флагам пользователя (битовая маска)
 *
 */
class UserSearchFlag {
	private $_top;
	/**
	 * Ссылка на дочерний клсс UserSearch
	 *
	 * @var UserSearch
	 */
	private $_user_search;
	
	function __construct($top,$user_search) {
		$this->_top = $top;
		$this->_user_search = $user_search;
	}
	
	public function __set($param,$value)
	{
		$this->_user_search->addFlagCondition($param,$value);
	}
	
}

class User extends A_SingleContent 
{
	private 
		$_profile_class;

	protected function loader()
	{
		$user_data = $this->_top->_sql->exe("SELECT * FROM ".TAB_PREF."user_list WHERE user_id={$this->_content_id}");
		if (count($user_data)==0) {
			throw $this->_top->Utils->PushError("User NOT FOUND","Пользователь по указанному user_id не найден");
		} else {
			$this->_data = $user_data[0];
			$profile_class = $this->_top->UserList->_profile_class_name;
			$this->_profile_class = new $profile_class($this->_top,$this,$this->_content_id);
		}
	}

	public function __get($param)
	{
		if ($param=="flag") { 
			return new UserFlags($this->_top,$this);
		} elseif (!isset($this->_data[$param])) { 
			return $this->_profile_class->$param;
		} else {
			return $this->_data[$param];
		}
	}

	public function __set($param,$value)
	{
		if ($param=="user_id" || $param=="login") {
			return false;
		} elseif (!isset($this->_data[$param])) {
//		      Zend_Debug::dump($this->_data);
//		      echo "я тут ($param = $value)";
			$this->_profile_class->$param = $value;
		}else{
			$value = $this->_top->Utils->prepareValueForSQL($value);
			
			if ($param=="password")
				$value = $this->_top->UserList->_password_md5==1 ? strtoupper(md5($value)) : $value;
				
			$this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."user_list SET $param='$value' WHERE user_id={$this->_content_id}");
			$this->_data[$param] = $value;
			return true;
		}
	}

	protected function deleter()
	{
		$this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."user_list WHERE user_id={$this->_content_id}");
		$this->_profile_class->delete();
	}


	/**
	 * Проверяет совпадение пароля
	 *
	 * @param string $password - Проверяемый пароль
	 * @return boolean - true в случае успешной проверки
	 */
	public function Auth($password)
	{
		 $password = $this->_top->UserList->_password_md5==1 ? strtoupper(md5($password)) : $password;
		 return ($password==$this->password); 
	}
}

class UserFlags
{
	private 
		$_top,
		$_parent;
		
	public function __construct($top,$parent)
	{
		$this->_top = $top;
		$this->_parent = $parent;
	}

	public function __get($param)
	{
        if( $param == 'value' ){ //надо получить просто значение флага "как есть"
            return $this->_parent->flags;
        }else
		  return $this->_top->BitMask->bitValue($this->_parent->flags,$this->_top->UserList->getFlagIndex($param));
	}

	public function __set($param,$value)
	{
        if( $param == 'value'){ //просто значение, как есть
            $this->_parent->_flags = $value;
        }else{
            $flagIndex = $this->_top->UserList->getFlagIndex($param);
		
            if ($value==1) {
                $this->_parent->flags = $this->_top->BitMask->setOn($this->_parent->flags,$flagIndex);
            } elseif ($value==0) {
                $this->_parent->flags = $this->_top->BitMask->setOff($this->_parent->flags,$flagIndex);
            }
        }
	}
	
}

abstract class A_UserProfile
{
	protected 
		$_top,
		$_parent,
		$_user_id,
		$_profile_data;

	public function __construct($top,$parent,$user_id) {
		$this->_top = $top;
		$this->_parent = $parent;
		$this->_user_id = $user_id;
		$this->loader();
	}

	abstract protected function loader();
	abstract public function __get($param);	
	abstract public function __set($param,$value);
	abstract public function delete();
}

?>